/*************************************************
 * IAP 升级程序的 bootloader
 * 
 * CPU:     STM32F103C8
 * 
 * ************************************************
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stm32f10x.h"
#include "systick.h"
#include "shell.h"
#include "zmodem.h"
#include "Ymodem.h"
#include "flash.h"
#include "ring_buffer.h"

#define COMM_UART USART1 // 和上位机通信的串口

typedef struct
{
    uint16_t is_valid; // 是否有效(flash必须以16位为单位进行写，故用uint16)
    uint32_t app_ep;   // app入口地址
    uint32_t app_size; // app大小
} AppInfo_t;

/* bootloader边界 */
uint32_t bl_limit = 0;
uint32_t app_min_addr = 0; // 允许的app最小地址

/* APP信息，存放在bootloader后的第4字节 */
#define APP_INFO ((AppInfo_t*)((bl_limit & ~0x01) + 4))

uint32_t app_ep   = 0; // app入口地址，默认0x08008000
uint32_t app_size = 0; // app大小 beytes

#define RX_BUF_LEN 512
uint8_t rx_buf_data[RX_BUF_LEN] = { 0 };
ring_buffer rx_buf = {0};

static void _Usart1Init(uint32_t baudrate);
static void _JumpToApplication(uint32_t appxaddr);

int main()
{
    /* 获取bootloader边界 */
    #ifdef __ARMCC_VERSION
    extern int Load$$LR$$LR_IROM1$$Limit;
    bl_limit = (uint32_t)&Load$$LR$$LR_IROM1$$Limit;
    #elif defined(__GNUC__)
    #elif defined(__ICCARM__)
    #endif

    /* 计算app最小地址 = 存放APP信息所在的FLASH页的下一页FLASH */
    app_min_addr = Flash_GetPageLimit((uint32_t)APP_INFO + sizeof(AppInfo_t) - 1) + 1;

    /* 默认app入口地址为app_min_addr */
    app_ep = app_min_addr;

    /* 关中断 */
    __disable_irq();

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    Systick_Init();

    ring_buffer_init(&rx_buf, rx_buf_data, RX_BUF_LEN);

    _Usart1Init(115200);

    /* 开中断 */
    __enable_irq();

#ifdef USED_FINSH
    /* 初始化 finsh */
    finsh_init();
#endif // USED_FINSH

    /* 读取FLASH中的APP信息，如果有效则加载 */
    if(APP_INFO->is_valid == 1)
    {
        app_ep   = APP_INFO->app_ep;
        app_size = APP_INFO->app_size;
    }

    /* 判断是否进入bootloader */
    uint32_t pre_tick = Systick_GetTick();
    uint8_t is_bl = 0; // 是否进入bootloader
    while(Systick_GetTickDiff(pre_tick) < 1000)
    {
        if(USART_ReceiveData(COMM_UART) == ' ') // 如果输入空格，则进入bootloader
        {
            is_bl = 1;
            break;
        }
    }

    if(!is_bl)
    {
        /* 进入应用程序 */
        _JumpToApplication(app_ep);
    }

    printf("Enter bootloader\r\n");

#ifdef USED_FINSH
    /* 启动 finsh 命令行 */
    finsh_starte();
    while(1){}
#else
    while(1)
    {
        
    }
#endif // USED_FINSH
}

#if defined(USED_ZMODEM)
/* zmodem接收程序 */
int Rz(int argc, char *argv[])
{
    uint32_t app_ep_temp = app_ep;
    if(argc > 1)
    {
        /* 保存应用程序入口地址 */
        app_ep_temp = strtoul(argv[1], NULL, 16);
        /* 检查镜像起始地址是否在允许的最小地址外 */
        if(app_ep_temp < app_min_addr)
        {
            printf("error: image enter point is less than 0x%08x\r\n", app_min_addr);
            return -1;
        }
    }
    else
    {
        /* 如果没有传入地址，默认烧写到允许的最小app地址 */
        app_ep_temp = app_min_addr;
    }

    /* 检查Flash是否处于上锁状态，是则解锁 */
    if(FLASH->CR & FLASH_CR_LOCK)
        FLASH_Unlock();

    Flash_Erase(app_ep_temp, FLASH_LIMIT - app_ep_temp);

    /* 升级程序 */
    printf("rz...\r\n");
    uint32_t size = zmodem_receive(app_ep_temp, RZSZ_FLAGS_NONE);

    /* 计算flash边界 */
    uint32_t flash_limit = FLASH_BASE + (FLASH_CAPACITY << 10);

    /* 是否超出flash边界 */
    if((app_ep_temp + size) >= flash_limit)
    {
        FLASH_Lock();
        printf("error: image exceeds the flash boundary: 0x%08x\r\n", flash_limit);
        return -1;
    }

#if defined(SHOW_RDATA)
    for(int i = 0; i < size; i++)
    {
        if((i % 1024) == 0) printf("\r\n%x:\r\n", app_ep_temp + i);
        printf("%02x ", ((uint8_t *)app_ep_temp)[i]);
    }
    printf("\r\n");
#endif
    printf("received %d bytes\r\n", size);

    /* 更新app入口地址和大小 */
    app_ep   = app_ep_temp;
    app_size = size;
    AppInfo_t app_info = { .is_valid = 1, .app_ep = app_ep, .app_size = app_size };

    uint32_t base = Flash_GetPageBase((uint32_t)APP_INFO);
    static uint16_t buf[FLASH_PAGE_SIZE / 2] = { 0 };
    for(int i = 0; i < FLASH_PAGE_SIZE/2; i++)
    {
        buf[i] = *((uint16_t *)base + i);
    }
    Flash_Erase(base, FLASH_PAGE_SIZE);
    Flash_Write(base, buf, (uint32_t)APP_INFO - base);
    Flash_Write((uint32_t)APP_INFO, (uint16_t *)&app_info, sizeof(AppInfo_t));
    FLASH_Lock();

    printf("app_ep: %08x, app_size: %dB\r\n", APP_INFO->app_ep, APP_INFO->app_size);

    return 0;
}
MSH_CMD_EXPORT_ALIAS(Rz, rz, "receive file through zmodem. use: rz [addr] addr is burned in address");
#endif // (defined(USED_ZMODEM))

#if (defined(USED_YMODEM))
/* ymodem接收程序 */
int Rb(int argc, char *argv[])
{
    uint32_t app_ep_temp = app_ep;
    if(argc > 1)
    {
        /* 保存应用程序入口地址 */
        app_ep_temp = strtoul(argv[1], NULL, 16);
        /* 检查镜像起始地址是否在允许的最小地址外 */
        if(app_ep_temp < app_min_addr)
        {
            printf("error: image enter point is less than 0x%08x\r\n", app_min_addr);
            return -1;
        }
    }
    else
    {
        /* 如果没有传入地址，默认烧写到允许的最小app地址 */
        app_ep_temp = app_min_addr;
    }

    /* 检查Flash是否处于上锁状态，是则解锁 */
    if(FLASH->CR & FLASH_CR_LOCK)
        FLASH_Unlock();

    /* 升级程序 */
    printf("rb...\r\n");
    int32_t size = Ymodem_Receive(app_ep_temp);

    /* 计算flash边界 */
    uint32_t flash_limit = FLASH_BASE + (FLASH_CAPACITY << 10);

    if(size == -1) // 如果下载文件太大 
    {
        printf("error: image exceeds the flash boundary: 0x%08x\r\n", flash_limit);
        return -1;
    }
    else if(size < 0) // 发送方终止或者写入错误
    {
        return -1;
    }

    #if defined(SHOW_RDATA)
    for(int i = 0; i < size; i++)
    {
        if((i % 1024) == 0) printf("\r\n%x:\r\n", app_ep_temp + i);
        printf("%02x ", ((uint8_t *)app_ep_temp)[i]);
    }
    printf("\r\n");
    #endif
    printf("received %d bytes\r\n", size);

    /* 更新app入口地址和大小 */
    app_ep   = app_ep_temp;
    app_size = size;
    AppInfo_t app_info = { .is_valid = 1, .app_ep = app_ep, .app_size = app_size };

    uint32_t base = Flash_GetPageBase((uint32_t)APP_INFO);
    static uint16_t buf[FLASH_PAGE_SIZE / 2] = { 0 };
    for(int i = 0; i < FLASH_PAGE_SIZE / 2; i++)
    {
        buf[i] = *((uint16_t *)base + i);
    }
    Flash_Erase(base, FLASH_PAGE_SIZE);
    Flash_Write(base, buf, (uint32_t)APP_INFO - base);
    Flash_Write((uint32_t)APP_INFO, (uint16_t *)&app_info, sizeof(AppInfo_t));
    FLASH_Lock();

    printf("app_ep: %08x, app_size: %dB\r\n", APP_INFO->app_ep, APP_INFO->app_size);

    return 0;
}
MSH_CMD_EXPORT_ALIAS(Rb, rb, "receive file through ymodem. use: rb [addr] addr is burned in address");
#endif // (defined(USED_YMODEM))

#if !(defined(USED_ZMODEM) || defined(USED_YMODEM))
    #error "Define at least one of the following macros: RX_BUF_SIZE, TX_BUF_SIZE"
#endif

/* 执行app程序 */
int GoApp(int argc, char **argv)
{
    printf("app enter point:%08x\r\n", app_ep);
    printf("sp:0x%08x, pc:0x%08x\r\n", *(__IO uint32_t *)app_ep, *(__IO uint32_t *)(app_ep + 4));
    printf("\r\n");

    /* 跳转 */
    _JumpToApplication(app_ep);

    return 0;
}
MSH_CMD_EXPORT_ALIAS(GoApp, go, "jump to application.");

static void _Usart1Init(uint32_t baudrate)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2ENR_USART1EN, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure = {
        .GPIO_Pin   = GPIO_Pin_9,
        .GPIO_Mode  = GPIO_Mode_AF_PP,
        .GPIO_Speed = GPIO_Speed_50MHz
    };
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate            = baudrate;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);

    /* 开接收中断 */
    USART_ClearFlag(USART1, USART_FLAG_RXNE);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_SetPriority(USART1_IRQn, 0);

    /* 使能串口 */
    USART_Cmd(USART1, ENABLE);
}

/**
 * @brief 跳转到APP
 * @param appxaddr 应用程序地址
 */
static void _JumpToApplication(uint32_t appxaddr)
{
    // 检查应用程序地址是否合法
    if(!(appxaddr >= 0x08000000) || !(appxaddr <= 0x0801FFFF))
        return;

    // 关闭中断
    __disable_irq();

    // 清除 NVIC 中断挂起标志
    for(int i = 0; i < 8; i++)
    {
        NVIC->ICPR[i] = 0xFFFFFFFF; // 清除所有中断挂起标志
    }

    // 关闭 SysTick 定时器
    SysTick->CTRL = 0x00;

    // 去初始化外设
    USART_DeInit(USART1);
    GPIO_DeInit(GPIOA);
    GPIO_DeInit(GPIOC);

    // 关闭开启的外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, DISABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, DISABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE);

    // 去初始化 RCC
    RCC_DeInit();

    // 设置向量表，请在 APP 中设置，如果在这里设置 SystemInit 会覆盖掉，所以需要在 APP 开始时设置向量表

    // 设置堆栈指针
    __set_MSP(*(__IO uint32_t *)appxaddr);

    // 跳转到应用程序
    __IO uint32_t app_reset_addr = *(__IO uint32_t *)(appxaddr + 4);
    void (*AppRestHandle)(void) = (void (*)(void))app_reset_addr;
    AppRestHandle();
}

int fputc(int ch, FILE *f)
{
    USART_SendData(COMM_UART, (uint8_t)ch);
    while(RESET == USART_GetFlagStatus(COMM_UART, USART_FLAG_TXE));
    return ch;
}

int fgetc(FILE *f)
{
    unsigned char c = 0;
    while(ring_buffer_read(&rx_buf, &c) != 0);
    return c;
}

void Usart_Handler(USART_TypeDef *usartx)
{
    if(usartx == COMM_UART)
    {
        if(USART_GetITStatus(COMM_UART, USART_IT_RXNE))
        {
            USART_ClearITPendingBit(COMM_UART, USART_IT_RXNE);
            ring_buffer_write(&rx_buf, (COMM_UART->DR & (uint16_t)0x01FF));
        }
    }
}
